package dkim

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	"git.oschina.net/chengzishuai/postman/src/net/smtpd/mail"
)

var (
	rxWSP  = regexp.MustCompile(`([ \t])+`)            // WSP = SP / HTAB
	rxLWSP = regexp.MustCompile(`((\r\n)?[ \t])`)      // LWSP = *(WSP / CRLF WSP)
	rxRWSP = regexp.MustCompile(`([ \t]+(\r\n))+`)     // RWSP = (WSP / CRLF)
	rxFWS  = regexp.MustCompile(`^([ \t]\r\n)?[ \t]+`) // FWS = [*WSP CRLF] 1*WSP
)

func Canonicalize(m *Mail, tp string) error {
	var signatureStr = ""
	switch tp {
	case "simple/simple":
		m.canonicalizedHeader = SimpleHeader(&m.m)
		signatureStr = "DKIM-Signature: v=1; " +
			"a=rsa-sha256; " +
			"c=simple/simple; " +
			"d=postman.xyz; " +
			"q=dns/txt; " +
			"s=515956d5cecd1d211b1629604ad9b61e; " +
			"t=%d; " +
			"bh=%s; " +
			"h=From:Subject:To; " +
			"b="
		canonicalizedBody := SimpleBody(&m.m)
		m.GetBodyHash(canonicalizedBody)
		m.signature = fmt.Sprintf(signatureStr, m.m.Date.Unix(), m.bodyHash)
		return nil
	case "simple/relaxed":
		m.canonicalizedHeader = SimpleHeader(&m.m)
		signatureStr = "DKIM-Signature: v=1; " +
			"a=rsa-sha256; " +
			"c=simple/relaxed; " +
			"d=postman.xyz; " +
			"q=dns/txt; " +
			"s=515956d5cecd1d211b1629604ad9b61e; " +
			"t=%d; " +
			"bh=%s; " +
			"h=From:Subject:To; " +
			"b="
		canonicalizedBody := RelaxedBody(&m.m)
		m.GetBodyHash(canonicalizedBody)
		m.signature = fmt.Sprintf(signatureStr, m.m.Date.Unix(), m.bodyHash)
		return nil
	case "relaxed/relaxed":
		m.canonicalizedHeader = RelaxedHeader(&m.m)
		signatureStr = "dkim-signature:v=1; " +
			"a=rsa-sha256; " +
			"c=relaxed/relaxed; " +
			"d=postman.xyz; " +
			"q=dns/txt; " +
			"s=515956d5cecd1d211b1629604ad9b61e; " +
			"t=%d; " +
			"bh=%s; " +
			"h=From:Subject:To; " +
			"b="
		canonicalizedBody := RelaxedBody(&m.m)
		m.GetBodyHash(canonicalizedBody)
		m.signature = fmt.Sprintf(signatureStr, m.m.Date.Unix(), m.bodyHash)
		return nil
	case "relaxed/simple":
		m.canonicalizedHeader = RelaxedHeader(&m.m)
		signatureStr = "dkim-signature:v=1; " +
			"a=rsa-sha256; " +
			"c=relaxed/simple; " +
			"d=postman.xyz; " +
			"q=dns/txt; " +
			"s=515956d5cecd1d211b1629604ad9b61e; " +
			"t=%d; " +
			"bh=%s; " +
			"h=From:Subject:To; " +
			"b="
		canonicalizedBody := SimpleBody(&m.m)
		m.GetBodyHash(canonicalizedBody)
		m.signature = fmt.Sprintf(signatureStr, m.m.Date.Unix(), m.bodyHash)
		return nil
	default:
		return errors.New("invalid canonicalization algorithm(s) for header and body")
	}
}

// The "simple" Header Canonicalization Algorithm
// According to RFC6376 https://tools.ietf.org/html/rfc6376#section-3.4.1
func SimpleHeader(m *mail.Mail) string {
	var header = ""
	// header += "Date: " + fmt.Sprintf("%d", m.Date.Unix()) + "\r\n"
	header += "From: " + m.From + "\r\n"
	header += "Subject: " + m.Subject + "\r\n"
	header += "To: " + m.To + "\r\n"
	return header
}

// The "relaxed" Header Canonicalization Algorithm
// Acording to RFC6376 https://tools.ietf.org/html/rfc6376#section-3.4.2
func RelaxedHeader(m *mail.Mail) string {
	var header = ""

	// header += "date:" + fmt.Sprintf("%d", m.Date.Unix()) + "\r\n"

	form := rxWSP.ReplaceAll([]byte(strings.TrimSpace(m.From)), []byte(" "))
	header += "from:" + fmt.Sprintf("%s", form) + "\r\n"

	subject := rxWSP.ReplaceAll([]byte(strings.TrimSpace(m.Subject)), []byte(" "))
	header += "subject:" + fmt.Sprintf("%s", subject) + "\r\n"

	to := rxWSP.ReplaceAll([]byte(strings.TrimSpace(m.To)), []byte(" "))
	header += "to:" + fmt.Sprintf("%s", to) + "\r\n"

	return header
}

// The "simple" Body Canonicalization Algorithm
// Acording to RFC6376 https://tools.ietf.org/html/rfc6376#section-3.4.3
func SimpleBody(m *mail.Mail) string {
	if len(m.Body) == 0 {
		return "\r\n"
	}
	body := regexp.MustCompile(`(\r\n)+$`).ReplaceAll([]byte(m.Body), []byte(""))
	return fmt.Sprintf("%s\r\n", body)
}

// The "relaxed" Body Canonicalization Algorithm
// Acording to RFC6376 https://tools.ietf.org/html/rfc6376#section-3.4.3
func RelaxedBody(m *mail.Mail) string {
	if len(m.Body) == 0 {
		return ""
	}
	var body []byte
	// Reduce all sequences of WSP within a line to a single SP character
	body = rxWSP.ReplaceAll([]byte(m.Body), []byte(" "))
	// Ignore all whitespace at the end of lines.
	body = rxRWSP.ReplaceAll(body, []byte("\r\n"))
	body = regexp.MustCompile(`(\r\n)+$`).ReplaceAll(body, []byte(""))
	return fmt.Sprintf("%s\r\n", body)
}
